package org.mm.proxycache;

import java.io.IOException;

import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoFuture;
import org.apache.mina.common.IoFutureListener;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.WriteFuture;
import org.mm.proxycache.http.HttpChunk;
import org.mm.proxycache.http.HttpRequest;
import org.mm.proxycache.http.HttpResponse;
import org.mm.proxycache.http.MutableHttpResponse;
import org.mm.proxycache.http.codec.HttpRequestDecoder;
import org.mm.proxycache.util.AccessLogger;
import org.mm.proxycache.util.AccessType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class ProxyServerHandler extends IoHandlerAdapter
{

    static Logger log = LoggerFactory.getLogger( ProxyServerHandler.class );

    @Inject
    private Provider<ConnectionManager> connectionManager;

    @Inject
    private AccessLogger accessLogger;

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.mina.common.IoHandlerAdapter#messageReceived(org.apache.mina.common.IoSession,
     *      java.lang.Object)
     */
    @Override
    public void messageReceived( IoSession serverSession, Object message ) throws Exception
    {
        if ( message instanceof HttpChunk )
        {
            HttpChunk chunk = (HttpChunk) message;
            handleChunk( serverSession, chunk );
            return;
        }

        log.debug( "response received" );
        IoSession clientSession = (IoSession) serverSession
                .getAttribute( ConnectionManager.ClientSessionName );

        MutableHttpResponse response = (MutableHttpResponse) message;

        String keepAliveTimeout = response.keepAliveValues().get( "timeout" );
        if ( keepAliveTimeout != null )
        {
            int timeout = Integer.parseInt( keepAliveTimeout );
            serverSession.getConfig().setBothIdleTime( Math.max( 0, timeout - 2 ) );
        }

        HttpRequest request = HttpRequestDecoder.getRequest( clientSession );

        if ( /* request.version() == HttpVersion.Http11 && */!response
                .hasHeader( "Content-Length" ) )
            response.header( "Transfer-Encoding", "chunked" );

        // XXX: Recheck this as I'm not sure
        // long length = response.getContent().remaining();
        // if ( !response.containsHeader( "Content-Length" ) )
        // response.setHeader( "Content-Length", Long.toString( length ) );

        response.removeHeader( "Connection" );
        response.header( "Proxy-Connection", request.isProxyKeepAlive() ? "Keep-Alive" : "Close" );

        clientSession.write( response );
    }

    private void handleChunk( final IoSession serverSession, final HttpChunk chunk )
    {
        final IoSession clientSession = (IoSession) serverSession
                .getAttribute( ConnectionManager.ClientSessionName );
        Preconditions.checkNotNull( clientSession );

        if ( chunk.isLast() )
        {
            log.debug( "Got a complete response" );

            if ( chunk.message().isKeepAlive() )
                connectionManager.get().release( serverSession );
            else
                serverSession.close();

            WriteFuture future = clientSession.write( chunk );

            future.addListener( new IoFutureListener<IoFuture>() {
                public void operationComplete( IoFuture future )
                {
                    HttpRequest request = HttpRequestDecoder.getRequest( clientSession );
                    accessLogger.log( AccessType.TCP_MISS, request, (HttpResponse) chunk.message(),
                            clientSession, serverSession );
                    clientSession.resumeRead();
                }
            } );

            HttpRequest request = HttpRequestDecoder.getRequest( clientSession );

            if ( !request.isProxyKeepAlive() )
                future.addListener( IoFutureListener.CLOSE );
        }
        else
            clientSession.write( chunk );
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.mina.common.IoHandlerAdapter#messageSent(org.apache.mina.common.IoSession,
     *      java.lang.Object)
     */
    @Override
    public void messageSent( IoSession session, Object message ) throws Exception
    {
        log.debug( "Message sent" );
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.mina.common.IoHandlerAdapter#sessionClosed(org.apache.mina.common.IoSession)
     */
    @Override
    public void sessionClosed( IoSession session ) throws Exception
    {
        log.debug( "Server Session to {} closed", session.getRemoteAddress() );
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.mina.common.IoHandlerAdapter#sessionOpened(org.apache.mina.common.IoSession)
     */
    @Override
    public void sessionOpened( IoSession session ) throws Exception
    {
        log.debug( "Session opened to {}", session.getRemoteAddress() );
    }

    @Override
    public void sessionIdle( IoSession session, IdleStatus status ) throws Exception
    {
        log.info( "Closing Idle connection." );
        session.close();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.mina.common.IoHandlerAdapter#exceptionCaught(org.apache.mina.common.IoSession,
     *      java.lang.Throwable)
     */
    @Override
    public void exceptionCaught( IoSession session, Throwable cause ) throws Exception
    {
        IoSession clientSession = (IoSession) session
                .getAttribute( ConnectionManager.ClientSessionName );

        if ( !(cause instanceof IOException) )
        {
            log.debug( "Exception:", cause );
        }

        clientSession.suspendRead();
        clientSession.close( true );
        session.close( true );
    }
}
